package com.shhic.hhplt.server.biz;

import java.io.*;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.xml.sax.SAXException;

import com.shhic.hhplt.server.exceptions.TestReportXmlException;
import com.shhic.hhplt.server.model.ProductBindingCode;
import com.shhic.hhplt.server.model.ProductSuite;
import com.shhic.hhplt.server.model.ProductTestResult;
import com.shhic.hhplt.server.model.TestResultEnum;
import com.shhic.hhplt.server.util.XMLDateUtil;
import com.shhic.hhplt.testreport.BindingCodeType;
import com.shhic.hhplt.testreport.StatusEnum;
import com.shhic.hhplt.testreport.TestItemOutput;
import com.shhic.hhplt.testreport.TestItemType;
import com.shhic.hhplt.testreport.TestReport;
import com.shhic.hhplt.testreport.TestSuiteType;
import com.shhic.hhplt.testreport.TestingProductType;

@Component
public class TestReportHelper implements ITestReportHelper {
	@Override
	public TestReport generateTestReportFromXml(String xmlStr) throws TestReportXmlException{
		try {
			JAXBContext context = JAXBContext.newInstance(TestReport.class);
			Unmarshaller shaller = context.createUnmarshaller();
			shaller.setSchema(SchemaFactory.newInstance(
					XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(new StreamSource(new ClassPathResource("/TestReportSchema.xsd").getInputStream())));
			InputStream stringXmlInputStream;
			stringXmlInputStream = new ByteArrayInputStream(xmlStr.getBytes(StandardCharsets.UTF_8));
			JAXBElement<TestReport> root = shaller.unmarshal(new StreamSource(stringXmlInputStream), TestReport.class);
			return root.getValue();
		} catch (SAXException | JAXBException | IOException e) {
			throw new TestReportXmlException(e);
		}
	}
	
	@Override
	public String generateXmlStrFromTestReport(TestReport report)
			throws TestReportXmlException {
		try {
			JAXBContext context = JAXBContext.newInstance(TestReport.class);
			Marshaller marshaller = context.createMarshaller();
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream(4096);
			marshaller.marshal(report,outputStream);
			String reportXmlStr = new String(outputStream.toByteArray(),"utf-8");
			return reportXmlStr;
		} catch (JAXBException e) {
			e.printStackTrace();
			throw new TestReportXmlException(e);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new TestReportXmlException(e);
		}
	}
	
	
	@Override
	public ProductTestResult generateProductTestResult(TestReport report) {
		ProductTestResult pr = new ProductTestResult();
		pr.setIdCode(report.getTestingProduct().getIdCode());
		pr.setTestTime(XMLDateUtil.convertToDate(report.getReportTime()));
		pr.setProductName(report.getTestingProduct().getProductName());
		
		if(report.getTestSuite().isEmpty())
			pr.setResult(TestResultEnum.UNTESTED);
		else{
			pr.setResult(TestResultEnum.TESTING);
			for(TestSuiteType t:report.getTestSuite()){
				if(t.getStatus() == StatusEnum.FAIL){
					pr.setResult(TestResultEnum.FAIL);
					break;
				}
			}
		}
		return pr;
	}
	
	
	@Override
	public List<ProductSuite> generateProductTestStatusList(TestReport report) {
		List<ProductSuite> pl = new ArrayList<ProductSuite>(report.getTestSuite().size());
		for(TestSuiteType t:report.getTestSuite()){
			ProductSuite p = new ProductSuite();
			p.setIdCode(report.getTestingProduct().getIdCode());
			p.setBarCode(t.getBarCode());	//对于单个测试项，有可能有条码，也可以没有
			p.setProductName(report.getTestingProduct().getProductName());			
			p.setResult(TestResultEnum.fromStr(t.getStatus().value()));
			p.setSuiteName(t.getName());
			p.setSuiteScriptVersion(t.getVersion());
			p.setTestTime(XMLDateUtil.convertToDate(t.getTestTime()));
			p.setWorkbay(t.getWorkbay());
			p.setTestor(t.getTestor());
			pl.add(p);
		}
		return pl;
	}

	@Override
	public TestReport mergeReport(TestReport oneReport, TestReport anotherReport) {
		//向oneReport进行merge
		for(TestSuiteType t:anotherReport.getTestSuite()){
			boolean shouldInsert = true;
			for(TestSuiteType o:oneReport.getTestSuite()){
				if(o.getName().equals(t.getName())){
					o.setTestor(t.getTestor());
					o.setTestTime(t.getTestTime());
					o.setVersion(t.getVersion());
					o.setWorkbay(t.getWorkbay());
					o.setBarCode(t.getBarCode());
					
					List<TestItemType> mergedTestItemList = mergeTestItemTypeList(o.getTestItem(),t.getTestItem());
					o.getTestItem().clear();
					o.getTestItem().addAll(mergedTestItemList);
					
					//要根据各项的情况，判断用例集是否成功。但如果新来的报告中已经标记为失败，那么就失败了
					if(t.getStatus() == StatusEnum.FAIL){
						o.setStatus(StatusEnum.FAIL);
					}else{
						o.setStatus(StatusEnum.PASS);
						for(TestItemType tit :o.getTestItem()){
							if(tit.getStatus() == StatusEnum.FAIL){
								o.setStatus(StatusEnum.FAIL);
								break;
							}else if(tit.getStatus() == StatusEnum.UNTESTED){
								o.setStatus(StatusEnum.UNTESTED);
							}
						}
					}
					
					shouldInsert = false;
				}
			}
			if(shouldInsert){
				oneReport.getTestSuite().add(t);
			}
		}
		return oneReport;
	}
	
	//合并测试项列表，第二个参数是新的
	private List<TestItemType> mergeTestItemTypeList(List<TestItemType> t1,List<TestItemType> t2){
		Map<String,TestItemType> tm1 = new HashMap<String, TestItemType>(),tm2 = new HashMap<String, TestItemType>();
		
		for(TestItemType tit1:t1){
			tm1.put(tit1.getName(), tit1);
		}
		for(TestItemType tit2:t2){
			tm2.put(tit2.getName(), tit2);
		}
		
		Set<String> names = new HashSet<String>();
		names.addAll(tm1.keySet());
		names.addAll(tm2.keySet());
		
		List<TestItemType> resultList = new LinkedList<TestItemType>();
		
		for(String n:names){
			TestItemType tit1 = tm1.get(n);
			TestItemType tit2 = tm2.get(n);
			if(tit2 == null || 
					tit2.getStatus() == StatusEnum.UNTESTED && tit1 != null){
				resultList.add(tit1);
			}else{
				resultList.add(tit2);
			}
		}
		return resultList;
	}

	@Override
	public String generateHtmlStrFromTestReport(TestReport testReport) {
		StringBuilder sb = new StringBuilder();
		TestingProductType product = testReport.getTestingProduct();
		sb.append("<table>")
			.append(String.format("<tr><td>产品类型</td><td>%s</td><td>标识码</td><td>%s</td></tr>",
					product.getProductName(),product.getIdCode()))
			.append("</table><hr/>");
		sb.append("<table>");
		for(TestSuiteType s:testReport.getTestSuite()){
			sb.append(String.format("<tr class='testSuite'><td>测试集合</td><td>%s</td><td>测试人员/工位</td><td>%s</td></tr>",
					s.getName(),s.getTestor()+"/"+s.getWorkbay()))
				.append(String.format("<tr><td>测试结果</td><td>%s</td><td>测试时间</td><td>%s</td></tr>",
						s.getStatus(),XMLDateUtil.convertToString(s.getTestTime())));
			
			StringBuilder tiSb = new StringBuilder();
			tiSb.append("<table class='testSuiteTable'>");
			for(TestItemType ti:s.getTestItem()){
				tiSb.append(String.format("<tr class='testItem'><td width=80>测试项名称</td><td width=200>%s</td><td width=50>测试结果</td><td>%s</td></tr>",
						ti.getName(),ti.getStatus()));
				for(TestItemOutput oi:ti.getOutput()){
					tiSb.append(String.format("<tr><td>输出项</td><td>%s</td><td>输出结果</td><td style='word-break:break-all'>%s</td></tr>",
							oi.getName(),oi.getValue()));
				}
				if(ti.getMessage() != null){
					tiSb.append(String.format("<tr><td>消息</td><td colspan=3>%s</td></tr>",
							ti.getMessage()));
				}
			}
			tiSb.append("</table>");
			sb.append(String.format("<tr><td>单项测试结果</td><td colspan=3>%s</td><tr>",tiSb.toString()));
		}
		sb.append("</table>");
		return sb.toString();
	}
	
	@Override
	public List<ProductBindingCode> generateBindingCodeList(
			TestReport testReport) {
		List<ProductBindingCode> pbcl = new LinkedList<ProductBindingCode>();
		
		List<BindingCodeType> bindingCode = testReport.getBindingCode();
		for(BindingCodeType b:bindingCode){
			ProductBindingCode pbc = new ProductBindingCode();
			pbc.setCode(b.getValue());
			pbc.setCodeName(b.getCodeName());
			pbc.setIdCode(testReport.getTestingProduct().getIdCode());
			pbc.setProductName(testReport.getTestingProduct().getProductName());
			pbc.setBindingTime(XMLDateUtil.convertToDate(testReport.getReportTime()));
			pbcl.add(pbc);
		}
		return pbcl;
	}
}
